#include "meshGen.hpp"
#include "meshTypeFunctions.hpp"
#include <catch2/catch_test_macros.hpp>

using namespace hms;

TEST_CASE("Generate UniMesh"){
	using M = MeshGenSettings;
	M s;
	s.meshType = M::MeshType::UniMesh;
	s.addBoundaryPatches = false;

	SECTION("from cell size and cell count"){
		s.useCellCount = true;
		s.useCellSize = true;
		s.nCellsX = 3;
		s.nCellsY = 4;
		s.dX = 0.5;
		s.dY = 0.4;
	}
	SECTION("from cell size and mesh size"){
		s.useCellCount = false;
		s.useCellSize = true;
		s.sizeX = 1.5;
		s.sizeY = 1.6;
		s.dX = 0.5;
		s.dY = 0.4;
	}
	SECTION("from mesh size and cell count"){
		s.useCellCount = true;
		s.useCellSize = false;
		s.nCellsX = 3;
		s.nCellsY = 4;
		s.sizeX = 1.5;
		s.sizeY = 1.6;
	}
	MeshVariant meshVar { generateMesh(s) };
	auto& mesh { std::get<UniMesh>(meshVar) };
	REQUIRE( mesh.nCellsX() == 3 );
	REQUIRE( mesh.nCellsY() == 4 );
	REQUIRE( mesh.dx() == 0.5 );
	REQUIRE( mesh.dy() == 0.4 );
}

TEST_CASE("Generate UniMesh with boundary patches"){
	using M = MeshGenSettings;
	M s;
	s.nCellsX = 3;
	s.nCellsY = 4;
	s.dX = 1;
	s.dY = 1;
	s.meshType = M::MeshType::UniMesh;
	s.addBoundaryPatches = true;

	auto helperMesh { uniMeshAs<UniMesh>(
		s.nCellsX, s.nCellsY, s.dX, s.dY, 1, false
	) };

	auto coords = [&](Index vert) -> Vector2s {
		return helperMesh.vertex(vert);
	};

	auto set = [&](M::PatchSettings& ps, Index beg, Index end){
		ps.beg = coords(beg);
		ps.end = coords(end);
	};

	SECTION("standard patches"){
		s.useStandardBoundaryPatches = true;

		MeshVariant meshVar { generateMesh(s) };

		std::visit( [](const auto& mesh){
			const BoundaryPatch
				& bottom { mesh.boundaryPatch("bottom") },
				& left   { mesh.boundaryPatch("left"  ) },
				& right  { mesh.boundaryPatch("right" ) },
				& top    { mesh.boundaryPatch("top"   ) };

			REQUIRE( (bottom.edges() == Array3i{ 0, 1, 2   }).all() );
			REQUIRE( (left  .edges() == Array4i{15,19,23,27}).all() );
			REQUIRE( (right .edges() == Array4i{18,22,26,30}).all() );
			REQUIRE( (top   .edges() == Array3i{12,13,14   }).all() );

			// mesh.logger().set_level( log::Level::info );
			// mesh.printInfo();
			// mesh.logger().set_level( log::Level::err );
		}, meshVar);
	}
	SECTION("custom patches"){
		s.useStandardBoundaryPatches = false;

		s.boundaryPatches.resize(4);

		M::PatchSettings
			&b {s.boundaryPatches[0]},
			&l {s.boundaryPatches[1]},
			&r {s.boundaryPatches[2]},
			&t {s.boundaryPatches[3]};

		b.name = "b";
		l.name = "l";
		r.name = "r";
		t.name = "t";
		b.clockwise = false;
		l.clockwise = false;
		r.clockwise = false;
		t.clockwise = false;

		auto test = [&](
			auto&& be,
			auto&& le,
			auto&& re,
			auto&& te
		){
			MeshVariant meshVar { generateMesh(s) };

			std::visit( [&](const auto& mesh){
				const BoundaryPatch
					& bp { mesh.boundaryPatch("b") },
					& lp { mesh.boundaryPatch("l") },
					& rp { mesh.boundaryPatch("r") },
					& tp { mesh.boundaryPatch("t") };
					
				auto report = []( const BoundaryPatch& p, const auto& edges ){
					if ( !(p.edges() == edges).all() )
						std::cout
						<< "target: " << edges.transpose()
						<< ", actual: " << p.edges().transpose()
						<< '\n';
				};

				report(bp, be);
				report(lp, le);
				report(rp, re);
				report(tp, te);

				REQUIRE( (bp.edges() == be).all() );
				REQUIRE( (lp.edges() == le).all() );
				REQUIRE( (rp.edges() == re).all() );
				REQUIRE( (tp.edges() == te).all() );
			}, meshVar);
		};

		SECTION("start at non-corner, counterclockwise"){
			/* start with a non-corner, bottom, ccwise */
			set(b, 1, 3);
			/* start with a non-corner, right, ccwise */
			set(r, 7, 19);
			/* start with a non-corner, top, ccwise */
			set(t, 18, 17);
			/* start with a non-corner, left, ccwise */
			set(l, 12, 4);

			test(
				Array2i{1,2}, // bottom
				Array2i{19,23}, // left
				Array3i{22,26,30}, // right
				Array1i{13} // top
			);
		}
		SECTION("start at corner, counterclockwise"){
			/* start with a corner, bottom, ccwise */
			set(b, 0, 3);
			/* start with a corner, right, ccwise */
			set(r, 3,15);
			/* start with a corner, top, ccwise */
			set(t,19,18);
			/* start with a corner, left, ccwise */
			set(l,16, 0);

			test(
				Array3i{0,1,2}, // bottom
				Array4i{15,19,23,27}, // left
				Array3i{18,22,26}, // right
				Array1i{14} // top
			);
		}

		b.clockwise = true;
		l.clockwise = true;
		r.clockwise = true;
		t.clockwise = true;

		SECTION("start at non-corner, clockwise"){
			/* start with a non-corner, bottom, cwise */
			set(b, 2, 1);
			/* start with a non-corner, left, cwise */
			set(l, 4,12);
			/* start with a non-corner, top, cwise */
			set(t,17,19);
			/* start with a non-corner, right, cwise */
			set(r, 7, 3);

			test(
				Array1i{1}, // bottom
				Array2i{19,23}, // left
				Array1i{18}, // right
				Array2i{13,14} // top
			);
		}
		SECTION("start at corner, clockwise"){
			/* start with a corner, bottom, cwise */
			set(b, 3, 2);
			/* start with a corner, left, cwise */
			set(l, 0,12);
			/* start with a corner, top, cwise */
			set(t,16,18);
			/* start with a corner, right, cwise */
			set(r,19,11);
			
			test(
				Array1i{2}, // bottom
				Array3i{15,19,23}, // left
				Array2i{26,30}, // right
				Array2i{12,13} // top
			);
		}
	}
	SECTION("cross-corner patches"){
		s.useStandardBoundaryPatches = false;
		s.boundaryPatches.emplace_back();
		M::PatchSettings& ps { s.boundaryPatches.back() };
		ps.name = "wrap-around";
		ps.clockwise = false;
		set(ps, 1, 0);

		MeshVariant meshVar { generateMesh(s) };

		std::visit( [&](const auto& mesh){
			const BoundaryPatch
				& p0 { mesh.boundaryPatch(ps.name + "_0") },
				& p1 { mesh.boundaryPatch(ps.name + "_1") },
				& p2 { mesh.boundaryPatch(ps.name + "_2") },
				& p3 { mesh.boundaryPatch(ps.name + "_3") };

			REQUIRE( (p0.edges() == Array2i{       1, 2}).all() );
			REQUIRE( (p1.edges() == Array4i{18,22,26,30}).all() );
			REQUIRE( (p2.edges() == Array3i{   12,13,14}).all() );
			REQUIRE( (p3.edges() == Array4i{15,19,23,27}).all() );

			// mesh.logger().set_level( log::Level::info );
			// mesh.printInfo();
			// for ( const BoundaryPatch& p : mesh.boundaryPatches() ){
			// 	mesh.logger().info(
			// 		"{}, edges {}",
			// 		p.name(), p.edges().transpose()
			// 	);
			// }
			// mesh.logger().set_level( log::Level::err );
		}, meshVar);

	}
}